#include "util.hpp"

const static std::string S1[4][4] ={{"01","00","11","10"},
                        {"11","10","01","00"},
                        {"00","10","01","11"},
                        {"11","01","00","10"}};
const static std::string S2[4][4] ={{"00","01","10","11"},
                        {"10","00","01","11"},
                        {"11","10","01","00"},
                        {"10","01","00","11"}};

// 因为解密真的只是改变了一个语句 其他的都是重复 
// 所以我的强迫症导致我受不了这么大规模的重复 必须得分模式复用
const static char ENCODE_MODE = 0;
const static char DECODE_MODE = 1;

const static size_t p10Table[10] = {3, 5, 2, 7, 4, 10, 1, 9, 8, 6};
const static size_t p8Table[8] = {6, 3, 7, 4, 8, 5, 10, 9};
const static size_t EP[8] = {4, 1, 2, 3, 2, 3, 4, 1};
const static size_t p4Table[4] = {2, 4, 3, 1};
const static size_t firstIp[8] = {2, 6, 3, 1, 4, 8, 5, 7};           // 第一次IP置换
const static size_t secondIp[8] = {4, 1, 3, 5, 7, 2, 8, 6};          // 第二次IP置换

// 修改密钥 进行置换 用于任意模式的P置换 不足 没有强行效验pTable和pLevel是否对应
std::string PFunc(const std::string& key, const size_t* const pTable, const size_t pLevel)
{
    // assert(pLevel == sizeof(pTable) / sizeof(pTable[0]));
    std::string result;
    for (size_t pos = 0; pos < pLevel; ++pos)
    {
        const size_t tmp = pTable[pos];
        assert(tmp <= key.length() && tmp > 0);
        result += key[tmp - 1];
    }
    return result;
}

// 用于 LS-1 LS-2 
std::string LSFunc(const std::string& key, const size_t num)
{
    std::string result;
    size_t cur = num % key.length();
    for (size_t pos = cur; pos < key.length(); ++pos)
    {
        result += key[pos];
    }
    for (size_t pos = 0; pos < cur; ++pos)
    {
        result += key[pos];
    }
    return result;
}

// 生成子密钥
// P10 35274,10,1986
// P8 637485,10,9
// P4 2431
// 主密钥 k1 和 k2
void CreateKey(const std::string& primaryKey, std::string& k1, std::string& k2)
{
    // 先进行P10
    std::string primaryKeyP10 = PFunc(primaryKey, p10Table, 10);
    // 得到两个左移一位的结果
    std::string primaryKeyL1 = LSFunc(primaryKeyP10.substr(0, 5), 1);
    std::string primaryKeyL2 = LSFunc(primaryKeyP10.substr(5, 5), 1);
    // 输出子密钥k1
    k1 = PFunc(primaryKeyL1 + primaryKeyL2, p8Table, 8);
    // 得到两个左移两位的结果
    std::string primaryKeyL3 = LSFunc(primaryKeyL1, 2);
    std::string primaryKeyL4 = LSFunc(primaryKeyL2, 2);
    // 输出子密钥k2
    k2 = PFunc(primaryKeyL3 + primaryKeyL4, p8Table, 8);
}

const size_t GetNum(const char& left, const char& right)
{
    if(left == '0')
    {
        if(right == '0')
            return 0;
        else if (right == '1')
            return 1;
    }
    else if(left == '1')
    {
        if(right == '0')
            return 2;
        else if(right == '1')
            return 3;
    }
}

std::string FFunc(const std::string& content, const std::string& key)
{
    // 拓展置换
    std::string source = PFunc(content, EP, 8);
    std::string tmp;
    // 异或
    for (size_t i = 0; i < 8; ++i)
    {
        tmp += (source[i] == key[i] ? '0': '1');
    }
    // std::cout << "tmp: " << tmp << std::endl;
    // 0 3  1 2
    // 获取S盒值
    std::string s1 = S1[GetNum(tmp[0], tmp[3])][GetNum(tmp[1], tmp[2])];
    std::string s2 = S2[GetNum(tmp[4], tmp[7])][GetNum(tmp[5], tmp[6])];
    // P4置换得到最后结果
    // std::cout << s1 << " " << s2 << std::endl;
    return PFunc(s1 + s2, p4Table, 4);
}

std::string SDES(const std::string& content, const std::string& primaryKey, const char mode)
{
    std::string k1, k2;
    std::string result;

    // 得到子密钥
    // 如果是加密模式 正着来 解密模式 反着来 其他都一模一样!
    if(ENCODE_MODE == mode) CreateKey(primaryKey, k1, k2);
    else if(DECODE_MODE == mode) CreateKey(primaryKey, k2, k1);
    for (size_t pos = 0; pos < content.length(); ++pos)
    {
        char item = content[pos];
        
        // 将item转化为二进制序列 其实我感觉直接用原生的char会更快 但是我不太想重构
        // 因为还有别的事 hh 
        std::string source;
        for (int num = 7; num >= 0; --num)
        {
            source += (((item >> num) & 1) + '0');
        }
        // 第一次IP置换
        source = PFunc(source, firstIp, 8);
        // 切分得到 L0 R0
        std::string L0 = source.substr(0, 4);
        std::string R0 = source.substr(4, 4);
        std::string R0Tmp = FFunc(R0, k1);
        // std::cout << "第一次F: " << R0Tmp << std::endl;
        // L1 = R0 R1 = R0Tmp 异或 L0
        std::string L1 = R0;
        std::string R1;
        for (size_t i = 0; i < 4; ++i)
        {
            R1 += (R0Tmp[i] == L0[i] ? '0': '1');
        }

        std::string R1Tmp = FFunc(R1, k2);
        // std::cout << "第二次F: " << R1Tmp << std::endl;
        // R2 = R1 L2 = R1Tmp 异或 L1
        std::string R2 = R1;
        std::string L2;
        for (size_t i = 0; i < 4; ++i)
        {
            L2 += (L1[i] == R1Tmp[i] ? '0': '1');
        }
        // 最后解析二进制为字符
        std::string encodeStr = PFunc(L2 + R2, secondIp, 8);
        char encodeItem = 0;
        for (int num = 0; num < 8; ++num)
        {
            encodeItem |= ((1 & (encodeStr[num] - '0')) << (7 - num));
        }
        // std::cout << "L0: " << L0 << " R0: " << R0 << std::endl;
        // std::cout <<"L1: "<< L1 << " R1: " << R1 << std::endl;
        // std::cout << "L2: " << L2 << " R2: " << R2 << std::endl;
        // std::cout << encodeStr << std::endl;
        result += encodeItem;
    }
    return result;
}